# coding=utf-8

import redis

from setting import REDIS_HOST, REDIS_PORT, REDIS_DB, REDIS_EXPIRE


class Redis(object):
    """
    redis数据库操作
    """
    host = REDIS_HOST
    port = REDIS_PORT
    db = REDIS_DB
    pool = redis.ConnectionPool(host=host, port=port, password="", decode_responses=False,
                                max_connections=100)  # 最多可以有100个线程连接
    r = redis.StrictRedis(socket_timeout=1, connection_pool=pool)
    pool = redis.ConnectionPool(host=host, port=port, password="", decode_responses=True,
                                max_connections=100)  # 最多可以有100个线程连接
    r1 = redis.StrictRedis(socket_timeout=1, connection_pool=pool)

    @staticmethod
    def _get_r():
        return Redis.r

    @classmethod
    def ping(cls):
        r = cls._get_r()
        return r.ping()

    @classmethod
    def exists(cls, key):
        r = cls._get_r()
        return r.exists(key)

    @classmethod
    def set(cls, key, value, expire=None):
        """
        写入键值对
        """
        # 判断是否有过期时间，没有就设置默认值
        if expire == -1:  # expire为-1，设置为永久
            expire_in_seconds = None
        elif expire:
            expire_in_seconds = expire
        else:
            expire_in_seconds = REDIS_EXPIRE
        r = cls._get_r()
        return r.set(key, value, ex=expire_in_seconds)

    @classmethod
    def get(cls, key):
        """
        读取键值对内容
        """
        r = cls._get_r()
        value = r.get(key)
        return value.decode('utf-8') if value else value

    @classmethod
    def incrby(cls, key, value=1):
        """
        增加值，默认为1，同时incrby只能给整数增加值，返回增加后的结果
        """
        r = cls._get_r()
        return r.incrby(key, value)

    @classmethod
    def incrbyfloat(cls, key, value: float):
        """
        增加浮点数值,可以给整数或者浮点数增加,返回增加后的结果,该方法可能导致get方法有精度的损失
        """
        r = cls._get_r()
        return r.incrbyfloat(key, value)

    @classmethod
    def setnx(cls, key, value):
        """
        不存在时设置key-value
        失败返回0,成功返回1
        """
        r = cls._get_r()
        return r.setnx(key, value)

    @classmethod
    def set_with_expire(cls, key, value, expire=None):
        """
        不存在时设置key-value-time
        失败返回True,成功返回None
        """
        if expire == -1:  # expire为-1，设置为永久
            expire_in_seconds = None
        elif expire:
            expire_in_seconds = expire
        else:
            expire_in_seconds = REDIS_EXPIRE
        r = cls._get_r()
        return r.set(key, value, ex=expire_in_seconds, nx=True)

    @classmethod
    def mget(cls, keys):
        r = cls._get_r()
        return r.delete(*keys)

    @classmethod
    def hset(cls, key, field, value):
        """
        写入hash表
        """
        r = cls._get_r()
        return r.hset(key, field, value)

    @classmethod
    def hmset(cls, key, value):
        """
        读取指定hash表的所有给定字段的值
        values={
            "key1":int,
            "key2":str2,
            "key3":json.dumps(lis3),
            "key4":json.dumps(dic4)
        }
        """
        r = cls._get_r()
        value = r.hmset(key, value)
        return value

    @classmethod
    def hincrby(cls, key, field, amount):
        r = cls._get_r()
        return r.hincrby(key, field, amount)

    @classmethod
    def hget(cls, key, field):
        """
        读取指定hash表的键值
        """
        r = cls._get_r()
        value = r.hget(key, field)
        return value.decode('utf-8') if value else value

    @classmethod
    def hgetall(cls, key):
        """
        获取指定hash表所有的值
        注意取出的值为str类型，即使原来存的是数字类型
        """
        r = cls._get_r()
        result = r.hgetall(key)
        return dict([(key.decode('utf-8'), result[key].decode('utf-8')) for key in result])

    @classmethod
    def delete(cls, *names):
        """
        删除一个或者多个
        """
        r = cls._get_r()
        return r.delete(*names)

    @classmethod
    def hdel(cls, name, key):
        """
		删除指定hash表的键值
        """
        r = cls._get_r()
        return r.hdel(name, key)

    @classmethod
    def sadd(cls, name, *key):
        """
        集合中添加元素
        """
        r = cls._get_r()
        return r.sadd(name, *key)

    @classmethod
    def smembers(cls, name):
        """
        获取集合所有元素
        """
        r = cls._get_r()
        s = r.smembers(name)
        result = set([value.decode('utf-8') for value in s])
        return result

    @classmethod
    def srem(cls, key, value):
        """
        移除集合的value
        """
        r = cls._get_r()
        return r.srem(key, value)

    @classmethod
    def sismember(cls, name, value):
        """
        判断元素是否在集合中
        """
        r = cls._get_r()
        return r.sismember(name, value)

    @classmethod
    def sinter(cls, keys: list):
        """
        返回所有集合的交集,这里源码类型好像有点问题
        """
        r = cls._get_r()
        result = r.sinter(*keys)
        return set(value.decode('utf-8') for value in result)

    @classmethod
    def zadd(cls, name, value: dict, nx=False):
        """
        示例 zadd name1 {key1:score1,key2:score2}
        """
        r = cls._get_r()
        return r.zadd(name, value, nx)

    @classmethod
    def zscore(cls, name, value):
        """
        返回对应value的score
        """
        r = cls._get_r()
        return r.zscore(name, value)

    @classmethod
    def zrem(cls, name, *values):
        """
        zset中删除多个value zrem name1 v1 v2 v3
        """
        r = cls._get_r()
        return r.zrem(name, *values)

    @classmethod
    def zrangebyscore(cls, name, min_score, max_score):
        """
        返回min_score和max_score间分数的value值,更多用法看 https://blog.csdn.net/weixin_46307478/article/details/122953512
        min_score可以写为"-inf" min_score可以写为"inf"或"+inf"
        默认升序排序
        """
        r = cls._get_r()
        return r.zrangebyscore(name, min_score, max_score)

    @classmethod
    def zrevrangebyscore(cls, name, max_score, min_score, start, num, withscores):
        """
        返回小于等于max_score，大于等于min_score的分数的value值
        默认从start开始即offset偏移量，num获取数量，withscores是否跟上分数
        min_score可以写为"-inf" min_score可以写为"inf"或"+inf"
        默认降序排序
        """
        r = cls._get_r()
        return r.zrevrangebyscore(name, max_score, min_score, start, num, withscores)

    @classmethod
    def geoadd(cls, name, value):
        """
        添加坐标 name:g1 value[x,y,place_id]
        """
        r = cls._get_r()
        return r.geoadd(name, value)

    @classmethod
    def geosearch(cls, name, member=None, x=None, y=None, radius=None, width=None, height=None, sort=None, count=None,
                  withdist=False, withcoord=False, unit='m'):
        """
        获取附近的地点，member为成员，x、y为经纬度，两证只能选择一种，unit为单位
        指定width和height代表矩形范围，指定radius=111代表范围111km的圆形，两者只能选择一种
        一经度或者一纬度大约111km
        """
        r = Redis.r1
        result = r.geosearch(name, member, x, y, unit, radius, width, height, sort, count, withdist=withdist,
                             withcoord=withcoord)
        return result

    @classmethod
    def setbit(cls, key, offset, value):
        r = cls._get_r()
        return r.setbit(key, offset, value)

    @classmethod
    def bitfield(cls, key, type_with_count, offset):
        """
        type_with_count需要指定类型以及查询的个数 type默认有 'u'和'i' 分别代表 无符号和有符号
        offset为偏移量
        参考写法 'test' 'u3' 1 : 偏移量1，拿到三个连续无符号0或1 组成的十进制数
        """
        r = cls._get_r()
        value = r.bitfield(key).get(type_with_count, offset).execute()
        return value[1] if value else None

    @classmethod
    def register_script(cls, script, keys=[], args=[]):
        """
        执行lua脚本 script_text,[key1,key2..],[arg1,arg2..]
        返回结果根据lua脚本决定
        """
        r = cls._get_r()
        return r.register_script(script)(keys, args)

    @classmethod
    def expire(cls, name, expire=None):
        """
        设置过期时间
        """
        if expire:
            expire_in_seconds = expire
        else:
            expire_in_seconds = REDIS_EXPIRE
        r = cls._get_r()
        return r.expire(name, expire_in_seconds)
